package com.sdy.healthcode.h5.controller;

import com.sdy.common.model.BizException;
import com.sdy.common.model.Response;
import com.sdy.common.utils.Assert;
import com.sdy.common.utils.RandomUtil;
import com.sdy.common.utils.StringUtil;
import com.sdy.healthcode.biz.constants.Constants;
import com.sdy.healthcode.biz.constants.RedisConstants;
import com.sdy.healthcode.biz.model.User;
import com.sdy.healthcode.biz.service.ManagerService;
import com.sdy.healthcode.biz.service.SmsService;
import com.sdy.healthcode.biz.service.UserService;
import com.sdy.healthcode.h5.config.WxConfig;
import com.sdy.healthcode.h5.config.WxSession;
import com.sdy.mvc.controller.BaseController;
import com.sdy.redis.service.RedisService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import java.util.Map;

@RestController
@RequestMapping("/login")
@Api(tags = "登录接口")
public class LoginController extends BaseController {
    @Autowired
    private RedisService redisService;
    @Autowired
    private SmsService smsService;
    @Autowired
    private UserService userService;
    @Autowired
    private ManagerService managerService;
    @Value("${res.app.bindphone}")
    private Boolean bindphone;
    @Value("${server.servlet.session.cookie.name}")
    private String sessionName;
    @Value("${res.app.env.test:false}")
    private Boolean envTest;

    /**
     * 微信登录
     * @param code 微信code
     */
    @PostMapping("/wxLogin")
    @ApiOperation(value = "微信登录", notes = "登录参数根据小程序登录流程做相应修改")
    @ApiImplicitParam(value = "微信code", name = "code", required = true)
    public Response wxLogin(String code, HttpServletRequest request) throws BizException {
        Assert.isBlank(code, "微信code不能为空");
        WxSession wxSession = decryptWxId(code);
        Assert.isNull(wxSession, "微信会话解析失败。");
        request.getSession().setAttribute(Constants.SESSION_WX, wxSession);
        User user;
        if (bindphone) {
            // 需要绑定手机号
            user = userService.getUserByWxid(wxSession.getWxId());
            if (user == null) {
                return Response.error("未绑定微信，请先使用手机号登录", -999);
            }
        } else {
            user = userService.xGetOrCreateUserByWxid(wxSession.getWxId());
        }
        request.getSession().setAttribute(com.sdy.common.constant.Constants.SESSION_USER_ID, user.getId());
        return Response.success();
    }

    /**
     * 微信登录
     * @param code 微信code
     */
    @PostMapping("/wxOaLogin")
    @ApiOperation(value = "微信公众号登录")
    @ApiImplicitParam(value = "微信公众号code", name = "code", required = true)
    public Response wxOaLogin(String code, HttpServletRequest request) throws BizException {
        Assert.isBlank(code, "微信code不能为空");
        WxSession wxSession = decryptWxIdByOa(code);
        Assert.isNull(wxSession, "微信会话解析失败。");
        request.getSession().setAttribute(Constants.SESSION_WX, wxSession);
        User user;
        if (bindphone) {
            // 需要绑定手机号
            user = userService.getUserByWxid(wxSession.getWxId());
            if (user == null) {
                return Response.error("未绑定微信，请先使用手机号登录", -999);
            }
        } else {
            user = userService.xGetOrCreateUserByWxid(wxSession.getWxId());
        }
        request.getSession().setAttribute(com.sdy.common.constant.Constants.SESSION_USER_ID, user.getId());
        return Response.success();
    }
    
    private WxSession decryptWxId(String code) {
        WxSession wxSession = new WxSession();
        Map<String, Object> resp = WxConfig.requestMa(WxConfig.WxApi.CODE2SESSION, WxConfig.ReqMethod.GET, code);
        wxSession.setOpenId((String) resp.get("openid"));
        wxSession.setUnionId((String) resp.get("unionid"));
        wxSession.setSessionKey((String) resp.get("session_key"));
        return wxSession;
    }

    private WxSession decryptWxIdByOa(String code) {
        WxSession wxSession = new WxSession();
        Map<String, Object> resp = WxConfig.oaCodeToSession(code);
        wxSession.setOpenId((String) resp.get("openid"));
        wxSession.setUnionId((String) resp.get("unionid"));
        wxSession.setSessionKey((String) resp.get("access_token"));
        return wxSession;
    }

    /**
     * 发送短信验证码
     * @param phone 手机号
     */
    @PostMapping("/smsCode")
    @ApiOperation(value = "发送登录短信验证码", notes = "重新发送需要等待60秒")
    @ApiImplicitParam(value = "手机号", name = "phone", required = true)
    public Response smsCode(String phone) throws BizException {
        Assert.isTrue(phone == null || !phone.matches(Constants.REGEX_PHONE), "手机号格式错误");
        if (redisService.exists(RedisConstants.LOGIN_SMS_SEND_LOCK + phone)) {
            return Response.error("操作过于频繁，请稍后再试。");
        }
        String smsCode = createLoginSmsCode();
        redisService.set(RedisConstants.LOGIN_SMS_SEND_LOCK + phone, "1", Constants.LOGIN_SMS_CODE_SEND_LOCK_SECONDS);
        redisService.set(RedisConstants.LOGIN_SMS_CODE + phone, smsCode, Constants.LOGIN_SMS_CODE_EXPIRE_SECONDS);
        // 发送短信验证码
        smsService.sendSmsCode(phone, smsCode);
        return Response.success();
//        return Response.success("暂时给你看验证码：" + smsCode);
    }

    /**
     * 短信验证码登录
     * @param phone 手机号
     * @param smsCode 短信验证码
     */
    @PostMapping("/confirm")
    @ApiOperation(value = "登录验证")
    @ApiImplicitParams(value = {
            @ApiImplicitParam(value = "手机号", name = "phone", required = true),
            @ApiImplicitParam(value = "短信验证码", name = "smsCode", required = true)
    })
    public Response confirm(HttpServletRequest request, String phone, String smsCode) {
        if (phone == null || !phone.matches(Constants.REGEX_PHONE)) {
            return Response.error("手机号格式错误。");
        }
        if (checkErrorTimesOverflow(RedisConstants.LOGIN_FAILED_TIMES + phone)) {
            return Response.error("短信验证码错误次数过多，请" + Constants.LOGIN_FAILED_LOCK_MINUTE + "分钟后再试。");
        }
        if (StringUtil.isBlank(smsCode)) {
            return Response.error("短信验证码不能为空。");
        }
        WxSession wxSession = (WxSession) request.getSession().getAttribute(Constants.SESSION_WX);
//        if (StringUtil.isBlank(wxid)) {
//            return Response.error("页面信息已过期，请刷新重试。");
//        }
        String smsCode2 = redisService.get(RedisConstants.LOGIN_SMS_CODE + phone, String.class);
        if (smsCode2 == null) {
            return Response.error("短信验证码已失效。");
        }
        if (!smsCode.trim().equals(smsCode2)) {
            addErrorTimes(RedisConstants.LOGIN_FAILED_TIMES + phone);
            if (checkErrorTimesOverflow(RedisConstants.LOGIN_FAILED_TIMES + phone)) {
                return Response.error("短信验证码错误次数过多，请" + Constants.LOGIN_FAILED_LOCK_MINUTE + "分钟后再试。");
            }
            return Response.error("短信验证码不正确。");
        }
        redisService.del(RedisConstants.LOGIN_SMS_CODE + phone);
        redisService.del(RedisConstants.LOGIN_FAILED_TIMES + phone);
        User user = userService.xGetOrCreateUserByPhone(phone, wxSession == null ? null : wxSession.getWxId());
        request.getSession().setAttribute(com.sdy.common.constant.Constants.SESSION_USER_ID, user.getId());
        return Response.success(user.getId());
    }
    
    @GetMapping("/logout")
    @ApiOperation(value = "登出")
    public Response logout(HttpServletRequest request) {
        request.getSession().removeAttribute(com.sdy.common.constant.Constants.SESSION_USER_ID);
        return Response.success();
    }

    @GetMapping("/testLogin")
    @ApiOperation(value = "测试登录")
    @ApiImplicitParams(value = {
            @ApiImplicitParam(value = "用户id", name = "userId"),
            @ApiImplicitParam(value = "手机号", name = "phone"),
            @ApiImplicitParam(value = "微信openId", name = "openId")
    })
    public Response testLogin(HttpServletRequest request, Integer userId, String phone, String openId) {
        if (!envTest) {
            return Response.error("请在测试环境使用。");
        }
        User user = null;
        if (userId != null) {
            user = userService.getById(userId);
        } else if (StringUtil.isNotBlank(phone)) {
            user = userService.getUserByPhone(phone);
        } else if (StringUtil.isNotBlank(openId)) {
            user = userService.getUserByWxid(openId);
        }
        if (user != null) {
            request.getSession().setAttribute(com.sdy.common.constant.Constants.SESSION_USER_ID, user.getId());
        } else {
            return Response.error("用户不存在。");
        }
        return Response.success("测试登录成功。");
    }
    
    private void addErrorTimes(String key) {
        redisService.incr(key);
        redisService.expire(key, Constants.LOGIN_FAILED_LOCK_MINUTE * 60);
    }
    
    private boolean checkErrorTimesOverflow(String key) {
        Integer t = redisService.get(key, Integer.class);
        boolean overflow = t != null && t >= Constants.LOGIN_FAILED_MAX_TIMES;
        if (overflow) {
            if (redisService.ttl(key).equals(-1L)) {
                redisService.expire(key, Constants.LOGIN_FAILED_LOCK_MINUTE * 60);
            }
        }
        return overflow;
    }

    /**
     * 生成短信验证码
     */
    private String createLoginSmsCode() {
        return RandomUtil.produceNumber(6);
    }
}
